package com.zhitan.netty.service.impl;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.*;
import com.alibaba.fastjson.JSONObject;

import com.zhitan.netty.ChannelRepository;
import com.zhitan.netty.bean.*;
import com.zhitan.netty.enums.*;
import com.zhitan.netty.service.BaseChannelHandlerService;
import com.zhitan.netty.service.INettyServerHandlerService;
import com.zhitan.netty.util.Cp56Time2aUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.zhitan.netty.constant.DataMapKeyConstant.*;

/**
 * netty处理服务
 */
@Slf4j
@Service
public class NettyServerHandlerServiceImpl extends BaseChannelHandlerService implements INettyServerHandlerService {
    @Value("${ftp-server.downloadTimeout}")
    private int ftpDownloadTimeOut;

    /**
     * 处理netty的数据
     *
     * @param ctx          netty上下文
     * @param nettyMessage 数据
     */
    @Override
    public void handler(@NotNull ChannelHandlerContext ctx, @NotNull NettyMessage nettyMessage) throws InterruptedException {
        String logKey = UUID.fastUUID().toString();
        JSONObject msgBody = new JSONObject();
        FrameTypeEnum returnType = FrameTypeEnum.UNKNOWN;

        String originalData = nettyMessage.getOriginalData();
        log.debug("KEY=[{}],原始数据:{}", logKey, originalData);
        ByteBuf byteBuf = Unpooled.buffer();
        FrameTypeEnum frameType = FrameTypeEnum.getByCode(nettyMessage.getFrameType());
        Map<String, Object> dataMap = nettyMessage.getDataMap();
        log.debug("KEY=[{}],解析后的数据:{}", logKey, JSONObject.toJSONString(dataMap));
        String deviceNo = ByteBufUtil.hexDump((byte[]) dataMap.get(KEY_DEVICE_NO));
        short packageIndex = nettyMessage.getPackageIndex();
        byte resLength = frameType.getResLen();
        log.debug("KEY=[{}],处理数据,帧类型:{},名称:{},释义:{}", logKey, frameType.getCode(), frameType.getName(), frameType.getDesc());

        switch (frameType) {
            case UNKNOWN:
                log.error("处理数据帧失败,未知的帧类型;原始数据:{}", nettyMessage.getOriginalData());
                return;
            case LOGIN:
                // 0x01 绑定
                ChannelRepository.bind(ctx, deviceNo);
                Integer count = 1; //TODO 看桩存不存在 pileMapper.selectCount(Wrappers.<ChargingPile>lambdaQuery().eq(ChargingPile::getId, deviceNo));
                // 编写起始位信息
                writeByteBufStart(byteBuf, resLength, packageIndex, FrameTypeEnum.LOGIN_RES.getCode());
                // 设备编号
                byteBuf.writeBytes((byte[]) dataMap.get(KEY_DEVICE_NO));
                byte result_0x01;
                if (count != null && count > 0) {
                    byteBuf.writeByte(FrameRespEnum.R_0x00.getCode());
                    result_0x01 = FrameRespEnum.R_0x00.getCode();
                    log.debug("KEY=[{}],设备:[{}]登录成功", logKey, deviceNo);
                } else {
                    byteBuf.writeByte(FrameRespEnum.R_0x01.getCode());
                    result_0x01 = FrameRespEnum.R_0x01.getCode();
                    log.debug("KEY=[{}],设备:[{}]登录失败", logKey, deviceNo);
                }
                // 编写crc校验并重置读位置
                writeByteCRCAndResetReadIndex(byteBuf, resLength);

                msgBody.put(KEY_DEVICE_NO, deviceNo);
                msgBody.put(KEY_RESULT, result_0x01);
                returnType = FrameTypeEnum.LOGIN_RES;
                // 先返回对时操作
                handlerAsync(() ->
                {
                    try {
                        Thread.sleep(3000);
                        timing(deviceNo);
                        String ver = (String) dataMap.getOrDefault(KEY_SOFT_EDITION, "");
                        //TODO 根据桩号更新设备硬件号 pileMapper.updateHardwareVersionByDeviceNo(deviceNo, HexUtil.decodeHexStr(ver).trim());
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return null;
                });
                break;
            case HEART_BEAT:
                // 0x03
                ChannelRepository.bind(ctx, deviceNo);
                // 编写起始位信息
                writeByteBufStart(byteBuf, resLength, packageIndex, FrameTypeEnum.HEART_BEAT_RES.getCode());
                // 设备编号
                byteBuf.writeBytes((byte[]) dataMap.get(KEY_DEVICE_NO));
                // 枪号
                byteBuf.writeByte((byte) dataMap.get(KEY_GUN_NO));
                // 心跳应答置0
                byteBuf.writeByte(FrameRespEnum.R_0x00.getCode());
                // 编写crc校验并重置读位置
                writeByteCRCAndResetReadIndex(byteBuf, resLength);
                msgBody.put(KEY_DEVICE_NO, deviceNo);
                msgBody.put(KEY_GUN_NO, dataMap.get(KEY_GUN_NO));
                msgBody.put(KEY_RESULT, FrameRespEnum.R_0x00.getCode());
                returnType = FrameTypeEnum.HEART_BEAT_RES;
                handlerAsync(() ->
                {
                    //TODO 保存心跳
                    return null;
                });
                // 更新设备状态
                handlerAsync(() ->
                {
                    //TODO 更新设备状态为在线
                    return null;
                });
                // 更新价格
                handlerAsync(() ->
                {
                    setFeeModel(deviceNo);
                    return null;
                });
                break;
            case FEE_MODEL_CHECK_REQ:
                // 0x05 计费模型验证请求
                writeByteBufStart(byteBuf, resLength, packageIndex, FrameTypeEnum.FEE_MODEL_CHECK_RES.getCode());
                // 验证设备费用模型，直接返回false 响应设备编号
                byteBuf.writeBytes((byte[]) dataMap.get(KEY_DEVICE_NO));
                // 计费模型编号
                byteBuf.writeShortLE(0x0001);
                // 默认返回不一致，让桩拉取最新费率信息 00 正确 01不正确
                byteBuf.writeByte(FrameRespEnum.R_0x01.getCode());
                // 编写crc校验并重置读位置
                writeByteCRCAndResetReadIndex(byteBuf, resLength);

                msgBody.put(KEY_DEVICE_NO, deviceNo);
                msgBody.put(KEY_FEE_MODEL_NO, 0x0001);
                msgBody.put(KEY_RESULT, FrameRespEnum.R_0x01.getCode());
                returnType = FrameTypeEnum.FEE_MODEL_CHECK_RES;
                break;
            case FEE_MODEL_REQ:
                // 0x09 编写起始位信息
                writeByteBufStart(byteBuf, resLength, packageIndex, FrameTypeEnum.FEE_MODEL_RES.getCode());
                // 桩编号
                byteBuf.writeBytes((byte[]) dataMap.get(KEY_DEVICE_NO));
                // 计费模型编号
                byteBuf.writeShortLE(0x0001);
                // 更新价格
                setFeeModelInfo(getLogKey(), byteBuf, deviceNo);
                // 编写crc校验并重置读位置
                writeByteCRCAndResetReadIndex(byteBuf, resLength);
                msgBody.put(KEY_DEVICE_NO, deviceNo);
                msgBody.put(KEY_FEE_MODEL_NO, 0x0001);
                returnType = FrameTypeEnum.FEE_MODEL_RES;
                break;
            case UPLOAD_OFFLINE_DATA:
                // 0x13 实施数据
                //TODO  拿到订单数据，去处理
                ChargingOrder order = saveOrderProcess(deviceNo, originalData, dataMap, 1);
                if (ObjectUtils.isNotEmpty(order)) {
                    dataMap.put(KEY_START_TIME, order.getStarttime());
                }
                // 更新充电桩端口状态
                handlerAsync(() ->
                {
                    // TODO 更新充电桩端口状态
                    return null;
                });
                // websocket推送数据给小程序
                sendWebSocketMessage(dataMap, deviceNo);
                break;
            case CHARGING_HANDSHAKE:
            case PARAM_SET:
            case CHARGING_END:
            case ERROR_FRAME_DATA:
            case BMS_STOP:
            case BATTERY_CHARGER_STOP:
            case BMS_OUTPUT_DATA:
            case BMS_INFO:
            case PILE_START_CHARGING:
            case UPDATE_BALANCE_RES:
            case SYNC_CARD_RES:
            case CLEAR_CARD_RES:
            case GET_OFFLINE_CARD_DATA_RES:
            case SET_WORK_SETTING_RES:
            case TIME_SET_RES:
            case GROUND_LOCK_REQ:
            case PILE_RETURN_DATA:
            case REBOOT_RES:
            case OTA_RES:
                log.error("KEY=[{}],未处理CASE逻辑,帧类型:[{}],释义:{},原始报文:{}",
                        logKey, frameType.getCode(), frameType.getDesc(), originalData);
                break;
            case START_CHARGING_RES:
                // 0x33
                // 远程启动回复
                // 启动结果 0x00失败 0x01成功
                byte startResult_0x33 = (byte) dataMap.get(KEY_START_RESULT);
                StartChargingErrorEnum error;
                if (startResult_0x33 == (byte) 0x00) {
                    // 错误结果0x00无0x01设备编号不匹配0x02枪已在充电0x03设备故障0x04设备离线0x05未插枪
                    byte errorCode = (byte) dataMap.get(KEY_ERROR_CODE);
                    error = StartChargingErrorEnum.getByCode(errorCode);
                } else {
                    error = StartChargingErrorEnum.UNKNOWN;
                }
                String orderId = (String) dataMap.get(KEY_TRANSACTION_NO);
                // TODO SOCKET推送启动失败或者成功
                handlerAsync(() ->
                {
                    // TODO 成功启动后更新订单状态
                    return null;
                });
                break;
            case STOP_CHARGING_RES:
                // 0X35 远程停机命令回复
                // 处理端口停止响应状态
                updateDeviceStopState(deviceNo, dataMap);
                break;
            case SETTLEMENT_BILL:
                // 0x3B 交易记录
                // 业务处理
                ChargingOrder orderInfo = handleOrderOverState(dataMap);
                // 编写起始位信息
                writeByteBufStart(byteBuf, resLength, packageIndex, FrameTypeEnum.SETTLEMENT_BILL_CONFIRM.getCode());
                Object transactionNo_0x3B = dataMap.get(KEY_TRANSACTION_NO);
                byteBuf.writeBytes(HexUtil.decodeHex((String) transactionNo_0x3B));
                // 0x00 上传成功 0x01 非法账单
                byte result_0x3B = orderInfo == null ? FrameRespEnum.R_0x01.getCode() : FrameRespEnum.R_0x00.getCode();
                byteBuf.writeByte(result_0x3B);
                // 编写crc校验并重置读位置
                writeByteCRCAndResetReadIndex(byteBuf, resLength);

                msgBody.put(KEY_TRANSACTION_NO, transactionNo_0x3B);
                msgBody.put(KEY_DEVICE_NO, deviceNo);
                msgBody.put(KEY_RESULT, result_0x3B);
                returnType = FrameTypeEnum.SETTLEMENT_BILL_CONFIRM;

                break;
            case FEE_MODEL_SET_RES:
                // 0x57 计费模型应答
                log.debug("KEY=[{}],更新充电桩计费状态为需要更新", logKey);
                break;
        }
        String messageOrderNo = DEFAULT_ORDER_NO;
        if (ObjectUtils.isNotEmpty(dataMap.get(KEY_TRANSACTION_NO))) {
            messageOrderNo = (String) dataMap.get(KEY_TRANSACTION_NO);
        }
        PileStatusEnum pileStatus = PileStatusEnum.UNKNOWN;
        if (ObjectUtils.isNotEmpty(dataMap.get(KEY_GUN_STATUS))) {
            pileStatus = PileStatusEnum.getByCode((byte) dataMap.get(KEY_GUN_STATUS));
        }
        ChannelRepository.sendMsgToDevice(logKey, deviceNo, byteBuf);
    }

    /**
     * 发送获取实时数据命令0x12
     *
     * @param deviceNo 设备号
     * @param portNo   端口号/枪号
     */
    @Override
    public void sendGetRealTimeDataCMD(@NotNull String deviceNo, @NotNull int portNo) {
        if (null == deviceNo || "".equals(deviceNo)) {
            log.error("读取实时数据失败!设备号为空!");
            return;
        }
        Map<String, Object> msgBody = new ConcurrentHashMap<>();
        ByteBuf byteBuf = Unpooled.buffer();
        FrameTypeEnum cmd = FrameTypeEnum.READ_REAL_TIME_DATA;
        writeByteBufStart(byteBuf, cmd.getResLen(), getPackageIndex(), cmd.getCode());
        // 设备编号
        byteBuf.writeBytes(ByteBufUtil.decodeHexDump(deviceNo));
        // 枪号 1位BCD码
        byteBuf.writeByte(portNo);
        writeByteCRCAndResetReadIndex(byteBuf, FrameTypeEnum.READ_REAL_TIME_DATA.getResLen());

        msgBody.put(KEY_DEVICE_NO, deviceNo);
        msgBody.put(KEY_GUN_NO, portNo);

        saveMessage(
                deviceNo,
                ByteBufUtil.hexDump(byteBuf),
                DEFAULT_ORDER_NO,
                msgBody.toString(),
                cmd,
                PileStatusEnum.UNKNOWN);

        ChannelRepository.sendMsgToDevice(getLogKey(), deviceNo, byteBuf);
    }

    /**
     * 远程账户余额更新 0x42
     * 平台在用户完成充值后会将用户更新的余额下发到充电桩,
     * 桩接收到此数据帧需要对当前充电用户的信息进行校验并更新余额信息
     *
     * @param deviceNo 设备号
     * @param amount   金额
     */
    @Override
    @Deprecated
    public void updateUserAmount(@NotNull String deviceNo, @NotNull BigDecimal amount) {
        if (null == deviceNo || "".equals(deviceNo)) {
            log.error("远程账户余额更新失败!设备号为空!");
            return;
        }
        if (null == amount) {
            log.error("远程账户余额更新失败!余额为null!");
            return;
        }

        ByteBuf byteBuf = Unpooled.buffer();
        FrameTypeEnum cmd = FrameTypeEnum.UPDATE_REMOTE_BALANCE_REQ;
        writeByteBufStart(byteBuf, cmd.getResLen(), getPackageIndex(), cmd.getCode());
        // 设备编号 7位BCD码
        byteBuf.writeBytes(ByteBufUtil.decodeHexDump(deviceNo));
        // 枪号 1位BCD码
        byteBuf.writeByte(FrameRespEnum.R_0x01.getCode());
        //卡号 8位BIN码 不足补0 如果不为0，需要校验本次充电是否为此卡充电；如果为0，则不校验，直接更新装当前充电用户余额
        byteBuf.writeBytes(ByteBufUtil.decodeHexDump(DEFAULT_PHYSICAL_CARD_NO));
        //余额 4位BIN码 保留两位小数
        byteBuf.writeShortLE(amount.multiply(BigDecimal.valueOf(100.0)).intValue());
        writeByteCRCAndResetReadIndex(byteBuf, cmd.getResLen());

        Map<String, Object> msgBody = new ConcurrentHashMap<>();
        msgBody.put(KEY_DEVICE_NO, deviceNo);
        msgBody.put(KEY_GUN_NO, FrameRespEnum.R_0x01.getCode());
        msgBody.put(KEY_PHYSICAL_CARD_NO, DEFAULT_PHYSICAL_CARD_NO);
        msgBody.put(KEY_TOTAL_AMOUNT, amount);

        saveMessage(
                deviceNo,
                ByteBufUtil.hexDump(byteBuf),
                DEFAULT_ORDER_NO,
                msgBody.toString(),
                cmd,
                PileStatusEnum.UNKNOWN);

        ChannelRepository.sendMsgToDevice(getLogKey(), deviceNo, byteBuf);
    }

    /**
     * 离线卡数据清除 0x46
     * 平台再充电桩在线时下发此数据帧，充电桩接收到报文后清除桩本地对应的离线卡数据
     *
     * @param deviceNo 设备号
     * @param cards    卡列表,卡号必须是8位
     */
    @Override
    @Deprecated
    public void clearOfflineCardData(@NotNull String deviceNo, List<String> cards) {
        if (null == deviceNo || "".equals(deviceNo)) {
            log.error("离线卡数据清除失败!设备号为空!");
            return;
        }

        if (cards.isEmpty()) {
            log.error("离线卡数据清除失败,离线卡数据为空！");
            return;
        }

        if (cards.size() > PHYSICAL_CARD_NO_LIST_MAX_SIZE) {
            log.error("离线卡数据清除失败,可清除离线卡列表个数最大为{}个,实际为{}个！", PHYSICAL_CARD_NO_LIST_MAX_SIZE, cards.size());
            return;
        }

        getOrClearOfflineCardData(deviceNo, cards, FrameTypeEnum.CLEAR_CARD_REQ);
    }

    /**
     * 离线卡数据查询 0x48
     * 平台再充电桩在线时下发此数据帧到充电桩，充电桩接收到该报文后查询桩本地是否存在对应的离线卡
     *
     * @param deviceNo 设备号
     * @param cards    卡列表,卡号必须是8位
     */
    @Override
    @Deprecated
    public void getOfflineCardData(String deviceNo, List<String> cards) {

        if (null == deviceNo || "".equals(deviceNo)) {
            log.error("离线卡数据查询失败!设备号为空!");
            return;
        }
        if (cards.isEmpty()) {
            log.error("获取离线卡数据失败,离线卡数据为空！");
            return;
        }

        if (cards.size() > PHYSICAL_CARD_NO_LIST_MAX_SIZE) {
            log.error("获取离线卡数据失败,离线卡列表个数最大为{}个,实际为{}个！", PHYSICAL_CARD_NO_LIST_MAX_SIZE, cards.size());
            return;
        }
        getOrClearOfflineCardData(deviceNo, cards, FrameTypeEnum.GET_OFFLINE_CARD_DATA_REQ);
    }

    /**
     * 获取或者清除离线卡数据
     *
     * @param deviceNo 设备号
     * @param cards    卡列表
     * @param cmd      命令 {@link FrameTypeEnum}
     */
    private void getOrClearOfflineCardData(String deviceNo, List<String> cards, FrameTypeEnum cmd) {
        int cardNosLength = 0;
        for (String card : cards) {
            cardNosLength = cardNosLength + card.length();
        }
        int dataLength = 8 + deviceNo.length() + cards.size() + cardNosLength;
        ByteBuf byteBuf = Unpooled.buffer();
        writeByteBufStart(byteBuf, Byte.parseByte(String.valueOf(dataLength)), getPackageIndex(), cmd.getCode());
        // 设备编号
        byteBuf.writeBytes(ByteBufUtil.decodeHexDump(deviceNo));
        // 数量
        byteBuf.writeByte(cards.size());
        for (String card : cards) {
            byteBuf.writeBytes(ByteBufUtil.decodeHexDump(card));
        }
        writeByteCRCAndResetReadIndex(byteBuf, dataLength);

        Map<String, Object> msgBody = new ConcurrentHashMap<>();
        msgBody.put(KEY_DEVICE_NO, deviceNo);
        msgBody.put(KEY_SIZE, cards.size());
        msgBody.put(KEY_PHYSICAL_CARD_NO, String.join(",", cards));

        saveMessage(
                deviceNo,
                ByteBufUtil.hexDump(byteBuf),
                DEFAULT_ORDER_NO,
                msgBody.toString(),
                cmd,
                PileStatusEnum.UNKNOWN);

        ChannelRepository.sendMsgToDevice(getLogKey(), deviceNo, byteBuf);
    }

    /**
     * 工作参数设置 0x52 采用云快充1.8版本
     * 远程设置充电桩是否停用；设置充电桩允许输出功率，以实现电网功率的调节
     *
     * @param deviceNo  设备ID
     * @param portNo    枪号
     * @param maxPower  允许最大功率 单位：kW
     * @param limitTime 限制时间 单位：分钟
     */
    @Override
    public void setDeviceWorkSetting(String deviceNo, Integer portNo, int maxPower, int limitTime) {
        if (null == deviceNo || "".equals(deviceNo)) {
            log.error("充电桩工作参数设置失败!设备号为空!");
            return;
        }
        if (null == portNo) {
            log.error("充电桩工作参数设置失败!充电枪号为空!");
            return;
        }
        if (maxPower <= 0) {
            log.error("充电桩工作参数设置失败!最大功率不能小于等于0!");
            return;
        }
        if (limitTime < 0) {
            log.error("充电桩工作参数设置失败!限制时间不能小于0!");
            return;
        }
        FrameTypeEnum cmd = FrameTypeEnum.SET_WORK_SETTING_REQ;
        ByteBuf byteBuf = Unpooled.buffer();
        writeByteBufStart(byteBuf, cmd.getResLen(), getPackageIndex(), cmd.getCode());
        // 设备编号 BCD 7
        byteBuf.writeBytes(ByteBufUtil.decodeHexDump(deviceNo));
        // 枪号 BIN 1
        byteBuf.writeByte(portNo);
        // 允许最大功率 BIN 2
        byteBuf.writeShortLE(maxPower);
        // 指令优先级 BIN 1 数字越大优先级越高 默认0
        byteBuf.writeByte(FrameRespEnum.R_0x01.getCode());
        // 限制时间 BIN 2 单位：分钟 默认0
        byteBuf.writeZero(limitTime);
        writeByteCRCAndResetReadIndex(byteBuf, cmd.getResLen());

        Map<String, Object> msgBody = new ConcurrentHashMap<>();
        msgBody.put(KEY_DEVICE_NO, deviceNo);
        msgBody.put(KEY_GUN_NO, portNo);
        msgBody.put(KEY_MAX_POWER, maxPower);
        msgBody.put(KEY_LIMIT_TIME, limitTime);

        saveMessage(
                deviceNo,
                ByteBufUtil.hexDump(byteBuf),
                DEFAULT_ORDER_NO,
                msgBody.toString(),
                cmd,
                PileStatusEnum.UNKNOWN
        );

        ChannelRepository.sendMsgToDevice(getLogKey(), deviceNo, byteBuf);
    }

    /**
     * 对时设置 0x56
     * 运营平台同步充电桩时钟，以确保充电桩与运营平台的时钟一致
     *
     * @param deviceNo 设备编号
     */
    @Override
    public void timing(String deviceNo) {
        Date now = new Date();
        FrameTypeEnum cmd = FrameTypeEnum.TIME_SET_REQ;
        ByteBuf byteBuf = Unpooled.buffer();
        writeByteBufStart(byteBuf, cmd.getResLen(), getPackageIndex(), cmd.getCode());
        byteBuf.writeBytes(HexUtil.decodeHex(deviceNo));
        byteBuf.writeBytes(HexUtil.decodeHex(Cp56Time2aUtil.date2HexStr(now)));
        writeByteCRCAndResetReadIndex(byteBuf, cmd.getResLen());

        Map<String, Object> msgBody = new ConcurrentHashMap<>();
        msgBody.put(KEY_DEVICE_NO, deviceNo);
        msgBody.put(KEY_DATE_TIME, now);

        saveMessage(
                deviceNo,
                ByteBufUtil.hexDump(byteBuf),
                DEFAULT_ORDER_NO,
                msgBody.toString(),
                cmd,
                PileStatusEnum.UNKNOWN
        );

        ChannelRepository.sendMsgToDevice(getLogKey(), deviceNo, byteBuf);
    }

    /**
     * 计费模型设置 0x58
     * 用户充电费用计算，每半小时为一个费率段，共48段，
     * 每段对应尖峰平谷其中一个费率充电时桩屏幕按此费率分别显示已充电费和服务费
     *
     * @param deviceNo 设备编号
     */
    @Override
    public void setFeeModel(@NotNull String deviceNo) {
        if (null == deviceNo || "".equals(deviceNo)) {
            log.error("计费模型设置失败!设备号为空!");
            return;
        }
        Integer needUpdate = 1;//pileMapper.queryNeedUpdate(deviceNo);
        // 2.0 如果需要更新价格，组合价格设置报文
        if (needUpdate.equals(1)) {
            ByteBuf byteBuf = Unpooled.buffer();
            FrameTypeEnum cmd = FrameTypeEnum.FEE_MODEL_SET_REQ;
            // 编写起始位信息
            writeByteBufStart(byteBuf, cmd.getResLen(), getPackageIndex(), cmd.getCode());
            byteBuf.writeBytes(HexUtil.decodeHex(deviceNo));
            // 计费模型编号
            byteBuf.writeShortLE(0x0001);
            // 编写crc校验并重置读位置
            writeByteCRCAndResetReadIndex(byteBuf, cmd.getResLen());

            Map<String, Object> msgBody = new ConcurrentHashMap<>();
            msgBody.put(KEY_DEVICE_NO, deviceNo);
            msgBody.put(KEY_SET_RESULT, 0x0001);

            saveMessage(
                    deviceNo,
                    ByteBufUtil.hexDump(byteBuf),
                    DEFAULT_ORDER_NO,
                    msgBody.toString(),
                    cmd,
                    PileStatusEnum.UNKNOWN
            );

            String logKey = getLogKey();
            setFeeModelInfo(logKey, byteBuf, deviceNo);
            ChannelRepository.sendMsgToDevice(logKey, deviceNo, byteBuf);
        }
    }

    /**
     * OTA升级
     *
     * @param reqs 更新请求
     */
    @Override
    public void ota(List<UpdatePileRequest> reqs) {
        //TODO 其他 逻辑
        // 判断是否需要升级
        for (UpdatePileRequest req : reqs) {
//            ChargingPile chargingPile = chargingPileMap.get(req.getPileId());
//            if (ObjectUtils.isEmpty(chargingPile)) {
//                log.error("桩编号：{},充电桩不存在！", req.getPileId());
//            }
            OTAFtpServer server = OTAFtpServer
                    .builder()
                    .host(req.getHost())
                    .port(req.getPort())
                    .userName(req.getUsername())
                    .password(req.getPassword())
                    .path(req.getFilePath())
                    .timeout(ftpDownloadTimeOut)
                    .build();
            //TODO 发送升级命令 senOTAUpdateCMD(chargingPile.getId(), chargingPile.getMaxPower(), server, req.getExecUploadType());
        }
    }

    /**
     * 启动充电
     *
     * @param deviceNo   设备号
     * @param devicePort 枪号
     * @param orderNo    订单号
     */
    @Override
    public void startCharging(String deviceNo, String devicePort, String orderNo, int amount) {
        ByteBuf byteBuf = Unpooled.buffer();
        FrameTypeEnum cmd = FrameTypeEnum.START_CHARGING_REQ;
        writeByteBufStart(byteBuf, cmd.getResLen(), getPackageIndex(), cmd.getCode());
        byteBuf.writeBytes(HexUtil.decodeHex(orderNo));
        byteBuf.writeBytes(HexUtil.decodeHex(deviceNo));
        byteBuf.writeByte(Integer.parseInt(devicePort));
        byteBuf.writeLongLE(0);
        byteBuf.writeLongLE(0);
        byteBuf.writeIntLE(amount);
        writeByteCRCAndResetReadIndex(byteBuf, cmd.getResLen());

        Map<String, Object> msgBody = new ConcurrentHashMap<>();
        msgBody.put(KEY_TRANSACTION_NO, orderNo);
        msgBody.put(KEY_DEVICE_NO, deviceNo);
        msgBody.put(KEY_GUN_NO, devicePort);
        msgBody.put(KEY_CARD_NO, 0);
        msgBody.put(KEY_PHYSICAL_CARD_NO, 0);
        msgBody.put(KEY_USER_BALANCE, amount);

        saveMessage(
                deviceNo,
                ByteBufUtil.hexDump(byteBuf),
                orderNo,
                msgBody.toString(),
                cmd,
                PileStatusEnum.UNKNOWN);

        ChannelRepository.sendMsgToDevice(getLogKey(), deviceNo, byteBuf);
    }

    /**
     * 启动充电
     *
     * @param deviceNo   设备号
     * @param devicePort 枪号
     */
    @Override
    public void endCharging(String deviceNo, String devicePort) {
        ByteBuf byteBuf = Unpooled.buffer();
        FrameTypeEnum cmd = FrameTypeEnum.STOP_CHARGING_REQ;
        writeByteBufStart(byteBuf, cmd.getResLen(), getPackageIndex(), cmd.getCode());
        byteBuf.writeBytes(HexUtil.decodeHex(deviceNo));
        byteBuf.writeByte(Integer.parseInt(devicePort));
        writeByteCRCAndResetReadIndex(byteBuf, cmd.getResLen());

        Map<String, Object> msgBody = new ConcurrentHashMap<>();
        msgBody.put(KEY_DEVICE_NO, deviceNo);
        msgBody.put(KEY_GUN_NO, devicePort);

        saveMessage(
                deviceNo,
                ByteBufUtil.hexDump(byteBuf),
                DEFAULT_ORDER_NO,
                msgBody.toString(),
                cmd,
                PileStatusEnum.UNKNOWN);

        ChannelRepository.sendMsgToDevice(getLogKey(), deviceNo, byteBuf);
    }

    @Override
    public void switchPile(List<SwitchPileRequest> reqs) {
        //TODO 切换桩状态 启动或者停止
    }
}
